#pragma once

#include "DCoordinates3.h"
#include <iostream>
#include <vector>
using namespace std;

namespace cagd
{
    // forward declaration of template class Matrix
    template <typename T>
    class Matrix;

    // forward declaration of template class TriangularMatrix
    template <typename T>
    class TriangularMatrix;

    // forward declaration of template class RowMatrix
    template <typename T>
    class RowMatrix;

    // forward declaration of template class ColumnMatrix
    template <typename T>
    class ColumnMatrix;

    // forward declarations of overloaded and templated input/output from/to stream operators
    template <typename T>
    std::ostream& operator << (std::ostream& lhs, const Matrix<T>& rhs);

    template <typename T>
    std::istream& operator >>(std::istream& lhs, Matrix<T>& rhs);

    //----------------------
    // template class Matrix
    //----------------------
    template <typename T>
    class Matrix
    {
        friend std::ostream& cagd::operator << <T>(std::ostream&, const Matrix<T>& rhs);
        friend std::istream& cagd::operator >> <T>(std::istream&, Matrix<T>& rhs);

    protected:
        unsigned int                    _row_count;
        unsigned int                    _column_count;
        std::vector< std::vector<T> >   _data;
    public:
        // special constructor (can also be used as a default constructor)
        Matrix(unsigned int row_count = 1, unsigned int column_count = 1);

        // copy constructor
        Matrix(const Matrix& m);

        // assignment operator
        Matrix& operator =(const Matrix& m);

        // get element by reference
        T& operator ()(unsigned int row, unsigned int column);

        // get copy of an element
        T operator ()(unsigned int row, unsigned int column) const;

        // get dimensions
        unsigned int GetRowCount() const;
        unsigned int GetColumnCount() const;

        // set dimensions
        virtual GLboolean ResizeRows(unsigned int row_count);
        virtual GLboolean ResizeColumns(unsigned int column_count);

        // update
        GLboolean SetRow(unsigned int index, const RowMatrix<T>& row);
        GLboolean SetColumn(unsigned int index, const ColumnMatrix<T>& column);

        // destructor
        virtual ~Matrix();
    };

    //--------------------------------
    // template class TriangularMatrix
    //--------------------------------
    template <typename T>
    class TriangularMatrix
    {
//        friend std::ostream& cagd::operator << <T>(std::ostream&, const TriangularMatrix<T>& rhs);
//        friend std::istream& cagd::operator >> <T>(std::istream&, TriangularMatrix<T>& rhs);

    protected:
        unsigned int                    _row_count;
        std::vector< std::vector<T> >   _data;

    public:
        // special constructor (can also be used as a default constructor)
        TriangularMatrix(unsigned int row_count = 1);

        // get element by reference
        T& operator ()(unsigned int row, unsigned int column);

        // get copy of an element
        T operator ()(unsigned int row, unsigned int column) const;

        // get dimension
        unsigned int GetRowCount() const;

        // set dimension
        bool ResizeRows(unsigned int row_count);
    };


    //-------------------------
    // template class RowMatrix
    //-------------------------
    template <typename T>
    class RowMatrix: public Matrix<T>
    {
    public:
        // special constructor (can also be used as a default constructor)
        RowMatrix(unsigned int column_count = 1);

        // get element by reference
        T& operator ()(unsigned int column);
        T& operator [](unsigned int column);

        // get copy of an element
        T operator ()(unsigned int column) const;
        T operator [](unsigned int column) const;

        // a row matrix consists of a single row
        GLboolean ResizeRows(unsigned int row_count);
    };

    //----------------------------
    // template class ColumnMatrix
    //----------------------------
    template <typename T>
    class ColumnMatrix: public Matrix<T>
    {
    public:
        // special constructor (can also be used as a default constructor)
        ColumnMatrix(unsigned int row_count = 1);

        // get element by reference
        T& operator ()(unsigned int row);
        T& operator [](unsigned int row);

        // get copy of an element
        T operator ()(unsigned int row) const;
        T operator [](unsigned int row) const;

        // a column matrix consists of a single column
        GLboolean ResizeColumns(unsigned int column_count);
    };

    ///////////////////////////////////////////////////////
    //---------------------------------------------------//
    // homework: implementation of template class Matrix //
    //---------------------------------------------------//
    ///////////////////////////////////////////////////////
    template <typename T>
    Matrix<T>::Matrix(unsigned int row_count, unsigned int column_count){
        _row_count = row_count;
        _column_count = column_count;

        _data.resize(_row_count);
        for (unsigned int r = 0; r < _row_count; ++r)
            _data[r].resize(_column_count);
    }

    // copy constructor
    template <typename T>
    Matrix<T>::Matrix(const Matrix& m):
            _row_count(m._row_count),
            _column_count(m._column_count),
            _data(m._data)
    {
    }

    // assignment operator
    template <typename T>
    Matrix<T>& Matrix<T>::operator =(const Matrix& m){

        if (this != &m)
        {
            _row_count = m._row_count;
            _column_count = m._column_count;
            _data = m._data;
        }
        return *this;
    }

    // get element by reference
    template <typename T>
    inline T& Matrix<T>::operator ()(unsigned int row, unsigned int column){
        return _data[row][column];
    }

    // get copy of an element
    template <typename T>
    inline T  Matrix<T>::operator ()(unsigned int row, unsigned int column) const{
        return _data[row][column];
    }

    // get dimensions
    template <typename T>
    inline unsigned int Matrix<T>::GetRowCount() const{
         return _row_count;
    }

    template <typename T>
    inline unsigned int Matrix<T>::GetColumnCount() const{
        return _column_count;
    }

    // set dimensions
    template <typename T>
    GLboolean Matrix<T>::ResizeRows(unsigned int row_count){
        if (_row_count != row_count)
        {
            _data.resize(row_count);
            for (unsigned int r = _row_count; r < row_count; ++r)
            {
                _data[r].resize(_column_count);
            }
            _row_count = row_count;
        }
        return GL_TRUE;
    }

    template <typename T>
    GLboolean Matrix<T>::ResizeColumns(unsigned int column_count)
    {
        if (_column_count != column_count)
        {
            _column_count = column_count;
            for (unsigned int r = 0; r < _row_count; ++r)
                       _data[r].resize(_column_count);
        }
        return GL_TRUE;
    }

    // update
    template <typename T>
    GLboolean Matrix<T>::SetRow(unsigned int index, const RowMatrix<T>& row){
          if ((index >= _row_count) || (row.GetColumnCount() != _column_count))
              return GL_FALSE;
          else{
             for(unsigned int c=0; c<_column_count; c++)
                 {
                 _data[index][c]=row[c];
             }
             return GL_TRUE;
        }
      }

    template <typename T>
    GLboolean Matrix<T>::SetColumn(unsigned int index, const ColumnMatrix<T>& column){
        if ((index >= _column_count) || (column.GetRowCount() != _row_count))
            return GL_FALSE;
        else{
           for(unsigned int r=0; r<_row_count; r++)
               {
               _data[r][index] = column[r];
           }
           return GL_TRUE;
        }
    }

    // destructor
    template <typename T>
    Matrix<T>::~Matrix() {
        _data.clear();
    }

    //--------------------------------------------------
    // implementation of template class TriangularMatrix
    //--------------------------------------------------

    // special constructor (can also be used as a default constructor)
    template <typename T>
    TriangularMatrix<T>::TriangularMatrix(unsigned int row_count): _row_count(row_count)
    {
         _data.resize(_row_count);
         for (unsigned int r = 0; r < row_count; ++r)
             _data[r].resize(r + 1);
    }

    // get element by reference
    template <typename T>
    T& TriangularMatrix<T>::operator ()(unsigned int row, unsigned int column)
    {
        return _data[row][column];
    }

    // get copy of an element
    template <typename T>
    T TriangularMatrix<T>::operator ()(unsigned int row, unsigned int column) const
    {
        return _data[row][column];
    }

    // get dimension
    template <typename T>
    unsigned int TriangularMatrix<T>::GetRowCount() const
    {
        return _row_count;
    }

    // set dimensions
    template <typename T>
    bool TriangularMatrix<T>::ResizeRows(unsigned int row_count)
    {
        if (_row_count != row_count)
        {
            _data.resize(row_count);

            for (unsigned int r = _row_count; r < row_count; ++r)
            {
                _data[r].resize(r + 1);
            }

            _row_count = row_count;
        }

        return true;
    }


    //////////////////////////////////////////////////////////
    //------------------------------------------------------//
    // homework: implementation of template class RowMatrix //
    //------------------------------------------------------//
    //////////////////////////////////////////////////////////
    // special constructor (can also be used as a default constructor)
    template <typename T>
    RowMatrix<T>::RowMatrix(unsigned int column_count):
            Matrix<T>(1, column_count)
    {
    }

    // get element by reference
    template <typename T>
    inline T& RowMatrix<T>::operator ()(unsigned int column){
        return this->_data[0][column];
    }

    template <typename T>
    inline T& RowMatrix<T>::operator [](unsigned int column){
        return this->_data[0][column];
    }

    // get copy of an element
    template <typename T>
    inline T RowMatrix<T>::operator ()(unsigned int column) const{
        return this->_data[0][column];
    }

    template <typename T>
    inline T RowMatrix<T>::operator [](unsigned int column) const{
        return this->_data[0][column];
    }

    // a row matrix consists of a single row
    template <typename T>
    GLboolean RowMatrix<T>::ResizeRows(unsigned int row_count){
        if(row_count != 1) return GL_FALSE;
        else {
            return GL_TRUE;
        }
    }

    /////////////////////////////////////////////////////////////
    //---------------------------------------------------------//
    // homework: implementation of template class ColumnMatrix //
    //---------------------------------------------------------//
    /////////////////////////////////////////////////////////////

    // special constructor (can also be used as a default constructor)
    template <typename T>
    ColumnMatrix<T>::ColumnMatrix(unsigned int row_count):
            Matrix<T>(row_count, 1)
    {
    }

    // get element by reference
    template <typename T>
    inline T& ColumnMatrix<T>::operator ()(unsigned int row){
        return this->_data[row][0];
    }
    template <typename T>
    inline T& ColumnMatrix<T>::operator [](unsigned int row){
        return this->_data[row][0];
    }

    // get copy of an element
    template <typename T>
    inline T ColumnMatrix<T>::operator ()(unsigned int row) const{
        return this->_data[row][0];
    }

    template <typename T>
    inline T ColumnMatrix<T>::operator [](unsigned int row) const{
        return this->_data[row][0];
    }

    // a column matrix consists of a single column
    template <typename T>
    GLboolean ColumnMatrix<T>::ResizeColumns(unsigned int column_count){
        if(column_count != 1) return GL_FALSE;
        else {
            return GL_TRUE;
        }
    }

    //------------------------------------------------------------------------------
    // definitions of overloaded and templated input/output from/to stream operators
    //------------------------------------------------------------------------------

    // output to stream
    template <typename T>
    std::ostream& operator <<(std::ostream& lhs, const Matrix<T>& rhs)
    {
        lhs << rhs._row_count << " " << rhs._column_count << std::endl;
        for (typename std::vector< std::vector<T> >::const_iterator row = rhs._data.begin();
             row != rhs._data.end(); ++row)
        {
            for (typename std::vector<T>::const_iterator column = row->begin();
                 column != row->end(); ++column)
                    lhs << *column << " ";
            lhs << std::endl;
        }
        return lhs;
    }

    // input from stream
    template <typename T>
    std::istream& operator >> (std::istream& lhs, Matrix<T>& rhs)
    {
        lhs >> rhs._row_count >> rhs._column_count;
        for (typename std::vector< std::vector<T> >::iterator row = rhs._data.begin();
             row != rhs._data.end(); ++row)
        {
            for (typename std::vector<T>::iterator column = row->begin();
                 column != row->end(); ++column)
                    lhs >> *column;
        }
        return lhs;
    }
}

